//The MIT License

//Copyright (c) 2009 Lluis Sanchez, Nick Darnell

//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:

//The above copyright notice and this permission notice shall be included in
//all copies or substantial portions of the Software.

//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//THE SOFTWARE.

using System;
using System.Reflection;
using System.Reflection.Emit;

namespace Polymorph
{
    public abstract class CodeExpression : CodeItem
    {
        public abstract Type ResultType
        {
            get;
        }

        public virtual void GenerateAsStatement(ILGenerator gen)
        {
            Generate(gen);
            gen.Emit(OpCodes.Pop);
        }

        public CodeExpression CallToString()
        {
            return new CodeMethodCall(this, "ToString");
        }

        public static CodeExpression operator ==(CodeExpression e1, CodeExpression e2)
        {
            return new CodeEquals(e1, e2);
        }

        public static CodeExpression operator !=(CodeExpression e1, CodeExpression e2)
        {
            return new CodeNotEquals(e1, e2);
        }

        public static CodeExpression operator >(CodeExpression e1, CodeExpression e2)
        {
            return new CodeGreaterThan(e1, e2);
        }

        public static CodeExpression operator <(CodeExpression e1, CodeExpression e2)
        {
            return new CodeLessThan(e1, e2);
        }

        public static CodeExpression operator >=(CodeExpression e1, CodeExpression e2)
        {
            return new CodeGreaterEqualThan(e1, e2);
        }

        public static CodeExpression operator <=(CodeExpression e1, CodeExpression e2)
        {
            return new CodeLessEqualThan(e1, e2);
        }

        public static CodeExpression operator !(CodeExpression e)
        {
            return new CodeNot(e);
        }

        public static CodeExpression operator +(CodeExpression e1, CodeExpression e2)
        {
            return new CodeAdd(e1, e2);
        }

        public static CodeExpression operator -(CodeExpression e1, CodeExpression e2)
        {
            return new CodeSub(e1, e2);
        }

        public static CodeExpression operator *(CodeExpression e1, CodeExpression e2)
        {
            return new CodeMul(e1, e2);
        }

        public static CodeExpression operator /(CodeExpression e1, CodeExpression e2)
        {
            return new CodeDiv(e1, e2);
        }

        public static CodeExpression operator %(CodeExpression e1, CodeExpression e2)
        {
            return new CodeMod(e1, e2);
        }

        public CodeExpression CastTo<T>()
        {
            return CastTo(typeof(T));
        }

        public CodeExpression CastTo(Type type)
        {
            return new CodeCast(type, this);
        }

        public CodeExpression And(CodeExpression other)
        {
            return new CodeAnd(this, other);
        }

        public CodeExpression Is<T>()
        {
            return new CodeIs(typeof(T), this);
        }

        public CodeExpression Is(Type type)
        {
            return new CodeIs(type, this);
        }

        public CodeExpression As<T>()
        {
            return new CodeAs(typeof(T), this);
        }

        public CodeExpression As(Type type)
        {
            return new CodeAs(type, this);
        }

        public CodeExpression NullCoalesce(CodeExpression e2)
        {
            return new CodeNullCoalesce(this, e2);
        }

        public CodeExpression Call(string name, params CodeExpression[] parameters)
        {
            return new CodeMethodCall(this, name, parameters);
        }

        public CodeExpression Call(MethodInfo method, params CodeExpression[] parameters)
        {
            return new CodeMethodCall(this, method, parameters);
        }

        public CodeFieldReference GetField(String name)
        {
            return GetField(name,
                BindingFlags.Public | BindingFlags.Instance |
                BindingFlags.NonPublic | BindingFlags.FlattenHierarchy);
        }

        public CodeFieldReference GetField(String name, BindingFlags flags)
        {
            FieldInfo field = ResultType.GetField(name, flags);

            if (field == null)
                throw new InvalidOperationException("Field '" + name + "' not found in " + ResultType);

            return new CodeFieldReference(this, field);
        }

        public CodePropertyReference GetProperty(String name)
        {
            return GetProperty(name,
                BindingFlags.Public | BindingFlags.Instance |
                BindingFlags.NonPublic | BindingFlags.FlattenHierarchy);
        }

        public CodePropertyReference GetProperty(String name, BindingFlags flags)
        {
            PropertyInfo property = ResultType.GetProperty(name, flags);

            if (property == null)
                throw new InvalidOperationException("Property '" + name + "' not found in " + ResultType);

            return new CodePropertyReference(this, property);
        }

        public CodeValueReference GetMember(string name)
        {
            return GetMember(name,
                BindingFlags.Public | BindingFlags.Instance |
                BindingFlags.NonPublic | BindingFlags.FlattenHierarchy);
        }

        public CodeValueReference GetMember(string name, BindingFlags flags)
        {
            MemberInfo[] mems = ResultType.GetMember(name, flags);

            if (mems.Length == 0)
                throw new InvalidOperationException("Field or Property '" + name + "' not found in " + ResultType);

            return GetMember(mems[0]);
        }

        public CodeValueReference GetMember(MemberInfo member)
        {
            if (member is FieldInfo)
            {
                return new CodeFieldReference(this, (FieldInfo)member);
            }
            else if (member is PropertyInfo)
            {
                return new CodePropertyReference(this, (PropertyInfo)member);
            }

            throw new InvalidOperationException(member.Name + " is not either a field or a property");
        }

        public CodeValueReference this[CodeExpression index]
        {
            get { return new CodeArrayItem(this, index); }
        }

        public CodeValueReference this[string name]
        {
            get { return GetMember(name); }
        }

        public CodeValueReference this[FieldInfo field]
        {
            get { return new CodeFieldReference(this, field); }
        }

        public CodeValueReference this[PropertyInfo prop]
        {
            get { return new CodePropertyReference(this, prop); }
        }

        public CodeExpression ArrayLength
        {
            get { return new CodeArrayLength(this); }
        }

        public CodeExpression IsNull
        {
            get { return new CodeEquals(this, new CodeLiteral(ResultType, null)); }
        }

        public bool IsNumber
        {
            get { return CodeGenerationHelper.IsNumber(ResultType); }
        }

        public override bool Equals(object obj)
        {
            return base.Equals(obj);
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }
    }
}